﻿//MemoryStatistics.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Logging;
	using StockSharp.Algo.Candles;
	using StockSharp.Algo.Strategies;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Класс отслеживания занимаемых объектов в памяти.
	/// </summary>
	public sealed class MemoryStatistics : BaseLogReceiver<MemoryStatistics>
	{
		/// <summary>
		/// Класс для отслеживания количества активных объектов конктретного типа.
		/// </summary>
		/// <typeparam name="T">Тип объекта.</typeparam>
		public class MemoryStatisticsValue<T>
		{
			private readonly MemoryStatistics _parent;
			private readonly CachedSynchronizedSet<T> _objects = new CachedSynchronizedSet<T>();

			internal MemoryStatisticsValue(MemoryStatistics parent)
			{
				if (parent == null)
					throw new ArgumentNullException("parent");

				_parent = parent;
			}

			/// <summary>
			/// Активные объекты.
			/// </summary>
			public T[] Objects
			{
				get { return _objects.Cache; }
			}

			/// <summary>
			/// Количество активных объектов.
			/// </summary>
			public int ObjectCount { get; private set; }

			/// <summary>
			/// Проверять, что удаляется ранее удаленный объект.
			/// </summary>
			public bool ThrowOnRemoveDeleted { get; set; }

			private bool _isEnabled;

			/// <summary>
			/// Включено ли отслеживание объектов, доступных через <see cref="Objects"/>. По-умолчанию, выключено.
			/// </summary>
			public bool IsEnabled
			{
				get { return _isEnabled; }
				set
				{
					if (!_isEnabled && value)
						Clear();

					_isEnabled = value;
				}
			}

			/// <summary>
			/// Добавить новый объект.
			/// </summary>
			/// <param name="obj">Новый объект.</param>
			public void Add(T obj)
			{
				ObjectCount++;

				if (_parent.IsTraceEnabled)
					_parent.AddDebugLog("Создан({1}) {0} типа {2}.", obj, ObjectCount, obj.GetType().Name);

				if (!IsEnabled)
					return;

				if (!_objects.TryAdd(obj))
					throw new ArgumentException("Объект {0} уже был ранее добавлен.".Put(obj));
			}

			/// <summary>
			/// Удалить активный объект.
			/// </summary>
			/// <param name="obj">Активный объект.</param>
			public void Remove(T obj)
			{
				ObjectCount--;

				if (_parent.IsTraceEnabled)
					_parent.AddDebugLog("Удален({1}) {0} типа {2}.", obj, ObjectCount, obj.GetType().Name);

				if (!IsEnabled)
					return;

				var found = _objects.Remove(obj);

				if (ThrowOnRemoveDeleted && !found)
					throw new ArgumentException("Объект {0} уже был ранее удален.".Put(obj));
			}

			/// <summary>
			/// Удалить активные объекты.
			/// </summary>
			/// <param name="objects">Активные объекты.</param>
			public void Remove(IEnumerable<T> objects)
			{
				var count = objects.Count();

				ObjectCount -= count;

				if (_parent.IsTraceEnabled)
					_parent.AddDebugLog("Удаление {0} объектов типа {1}.", count, typeof(T).Name);

				if (!IsEnabled)
					return;

				var hasDeleted = false;

				foreach (var obj in objects)
				{
					if (!_objects.Remove(obj))
						hasDeleted = true;
				}

				if (ThrowOnRemoveDeleted && hasDeleted)
					throw new ArgumentException("Некоторые объекты уже были ранее удалены.");
			}

			/// <summary>
			/// Очистить активные объекты <see cref="Objects"/>.
			/// </summary>
			/// <param name="resetCounter">Очищать ли счетчик объектов.</param>
			public void Clear(bool resetCounter = false)
			{
				// TODO: WeakReference?
				_objects.Clear();

				if (resetCounter)
					ObjectCount = 0;
			}
		}

		static MemoryStatistics()
		{
			Instance = new MemoryStatistics();
		}

		/// <summary>
		/// Объект класса <see cref="MemoryStatistics"/>.
		/// </summary>
		public static MemoryStatistics Instance { get; private set; }

		private DateTime _lastTime;

		private MemoryStatistics()
		{
			Traders = new MemoryStatisticsValue<ITrader>(this);
			Strategies = new MemoryStatisticsValue<Strategy>(this);
			Rules = new MemoryStatisticsValue<IMarketRule>(this);
			Trades = new MemoryStatisticsValue<Trade>(this);
			Candles = new MemoryStatisticsValue<Candle>(this);

			Interval = TimeSpan.FromSeconds(60);

			ThreadingHelper.Timer(() =>
			{
				if (DateTime.Now - _lastTime < Interval)
					return;

				_lastTime = DateTime.Now;

				this.AddInfoLog(ToString());

				if (Rules.ObjectCount > 0)
					this.AddDebugLog(GetStrategyAndRulesReport());

				if (!Rules.Objects.IsEmpty())
					this.AddDebugLog(GetRulesDump());

			}).Interval(TimeSpan.FromSeconds(10));
		}

		/// <summary>
		/// Интервал логирования статистики. По умолчанию 60 сек.
		/// </summary>
		public TimeSpan Interval { get; set; }

		/// <summary>
		/// Логировать создание и удаление объектов. По умолчанию false.
		/// </summary>
		public bool IsTraceEnabled { get; set; }

		/// <summary>
		/// Включить отслеживание определенных типов данных.
		/// </summary>
		/// <param name="pattern">I=<see cref="Traders"/>, S=<see cref="Strategies"/>, R=<see cref="Rules"/>, T=<see cref="Trades"/>, C=<see cref="Candles"/>
		/// и комбинации этих символов (например, SR). Пустая строка означает выключение отслеживания всех типов данных.</param>
		public void Enable(string pattern)
		{
			if (pattern == null)
				throw new ArgumentNullException("pattern");

			Traders.IsEnabled = pattern.Contains("I");
			Strategies.IsEnabled = pattern.Contains("S");
			Rules.IsEnabled = pattern.Contains("R");
			Trades.IsEnabled = pattern.Contains("T");
			Candles.IsEnabled = pattern.Contains("C");
			IsTraceEnabled = pattern.Contains("E");
		}

		/// <summary>
		/// Очистить статистику памяти.
		/// </summary>
		/// <param name="resetCounter">Очищать ли счетчик объектов.</param>
		public void Clear(bool resetCounter)
		{
			Traders.Clear(resetCounter);
			Strategies.Clear(resetCounter);
			Rules.Clear(resetCounter);
			Trades.Clear(resetCounter);
			Candles.Clear(resetCounter);
		}

		/// <summary>
		/// Статистика по шлюзам.
		/// </summary>
		public MemoryStatisticsValue<ITrader> Traders { get; private set; }

		/// <summary>
		/// Статистика по стратегиям.
		/// </summary>
		public MemoryStatisticsValue<Strategy> Strategies { get; private set; }

		/// <summary>
		/// Статистика по правилам.
		/// </summary>
		public MemoryStatisticsValue<IMarketRule> Rules { get; private set; }

		/// <summary>
		/// Статистика по сделкам.
		/// </summary>
		public MemoryStatisticsValue<Trade> Trades { get; private set; }

		/// <summary>
		/// Статистика по свечкам.
		/// </summary>
		public MemoryStatisticsValue<Candle> Candles { get; private set; }

		/// <summary>
		/// Получить строковое представление.
		/// </summary>
		/// <returns>Строковое представление.</returns>
		public override string ToString()
		{
			return "Шлюзов {0}, Стратегий {1}, Правил {2}, Сделок {3}, Свечек {4}."
				.Put(Traders.ObjectCount, Strategies.ObjectCount, Rules.ObjectCount, Trades.ObjectCount, Candles.ObjectCount);
		}

		/// <summary>
		/// Отчет по числу стратегиий и правил определенных типов.
		/// </summary>
		/// <returns>Отчет.</returns>
		public string GetStrategyAndRulesReport()
		{
			return "Стратегии и правила по типам"
			       + ByTypeReport("", Strategies.Objects,
			                      s => true,
			                      t => ByTypeReport("  ", Rules.Objects,
			                                        r => (r.Container != null ? r.Container.GetType() : null) == t, r => ""))
			       + "\nПравила по типам\n"
			       + ByTypeReport("", Rules.Objects, r => true, r => "");
		}

		/// <summary>
		/// Получить текстовый дамп правил.
		/// </summary>
		/// <param name="count">Максимальное количество правил для дампа.</param>
		/// <returns>Строка с дампом.</returns>
		public string GetRulesDump(int count = -1)
		{
			var rules = Rules.Objects;

			if (count < 0)
				count = rules.Length;

			var s = "Дамп " + count + " правил (из " + rules.Length + "):\n";

			var min = rules.Length < count ? 0 : (rules.Length - count) / 2;
			var max = Math.Min(min + count, rules.Length - 1);

			for (var i = min; i <= max; i++)
			{
				s = s + rules[i].Container + "," + rules[i].GetType().Name + "," + rules[i] + "," + rules[i].Token + "\n";
			}

			return s;
		}

		private static string ByTypeReport<T>(string tab, IEnumerable<T> objs, Func<T, bool> predicate, Func<Type, string> details)
			where T : class
		{
			var s = "";

			var map = new Dictionary<Type, int>();

			foreach (var obj in objs.Where(predicate))
				map[obj.GetType()] = map.SafeAdd(obj.GetType(), t => 0) + 1;

			foreach (var kv in map)
				s = s + tab + kv.Key.Name + ":" + kv.Value + "\n" + details(kv.Key);

			return s;
		}
	}
}